/*****************************************************\
* Copyright (c) 2020 The game2d Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/game2d.git
* Email:          505544956@qq.com
* FileName:       core/game.h
* Create Time:    2020/03/08 16:32:18
* Description:    /
\****************************************************/

#ifndef __GAME2D_GAME_H__
#define __GAME2D_GAME_H__

#include <map>
#include <vector>
#include <string>

#include <SDL.h>
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <SDL_mixer.h>

namespace g2d {
  class Game;

  enum class Result : int {
    E_NotExistedSprite        = -5003,
    E_NotExistedGameObject    = -5002,
    E_XmlFail                 = -4001,
    E_SfxNotFound             = -3002,
    E_MusicNotFind            = -3001,
    E_TextureNotFound         = -2001,
    E_FontNotFound            = -1001,
    E_Existed                 = -6,
    E_ExistedID               = -5,
    E_NotImplemented          = -4,
    E_InvalidParam            = -3,
    E_OutOfMemory             = -2,
    E_Fail                    = -1,
    S_OK                      = 0,
    S_Initialized             = 1,
    S_Existed                 = 2,
    S_NotExisted              = 3,
    S_FunctionNotOverride     = 4,
  };
  inline bool Succeeded(const Result rst) { return ((int)rst) >= 0; }
  inline bool Failed(const Result rst) { return ((int)rst) < 0; }

  ///////////////////
  class Texture {
    friend class Game;
    protected:
      Texture();
      virtual ~Texture();
    protected:
      Result init(SDL_Renderer *render, int flags);
      void uninit();
    public:
      Result load(const char *id, SDL_Texture *texture);
      Result load_file(const char *id, const char *file);
      // if type is 0, auto detect
      Result load_buffer(const char *id, const void* buffer, Uint32 len, const char *type = 0);
      void unload(const char *id);
      void clear();
      SDL_Texture* get(const char *id) const;
    public:
      Result size(const char *id, int *w, int *h) const;
      Result size(SDL_Texture *t, int *w, int *h) const;
    protected:
      SDL_Renderer *render_;
      std::map<std::string, SDL_Texture*> map_;
  };

  ///////////////////
  class Font {
    friend class Game;
    protected:
      Font();
      virtual ~Font();
    protected:
      Result init();
      void uninit();
    public:
      Result load(const char *id, const char *file, Uint32 size);
      Result load(const char *id, const char *file, Uint32 size, int style);
      Result load(const char *id, const char *file, Uint32 size
          , bool bold, bool italic, bool underline, bool strikethrough);
      void unload(const char *id);
      void clear();
      TTF_Font* get(const char *id) const;
    public:
      int style(const TTF_Font *font) const;
      void set_style(TTF_Font *font, int s);
      void set_style(TTF_Font *font, bool bold, bool italic, bool underline, bool strikethrough);
      int height(const TTF_Font *font) const;
      
      int style(const char *id) const;
      void set_style(const char *id, int s);
      void set_style(const char *id, bool bold, bool italic, bool underline, bool strikethrough);
      int height(const char *id) const;
    public:
      Result size(const char *font, const char *text, int *w, int *h);
      Result size(TTF_Font *font, const char *text, int *w, int *h);
    public:
      std::map<std::string, TTF_Font*> map_;
  };

  ///////////////////
  class Audio {
    friend class Game;
    protected:
      Audio();
      virtual ~Audio();
    protected:
      Result init(int flags, int frequency = 44100, int chunksize = 2048);
      void uninit();
    public:
      Result load_sfx_file(const char *id, const char *file);
      Result load_sfx_buffer(const char *id, const void* buffer, Uint32 len);
      Result load_music_file(const char *id, const char *file);
      Result load_music_buffer(const char *id, const void* buffer, Uint32 len);
      void unload_sfx(const char *id);
      void unload_music(const char *id);
      void clear_sfx();
      void clear_music();
      void clear();
      Mix_Chunk* get_sfx(const char *id) const;
      Mix_Music* get_music(const char *id) const;
    public:
      Result play_sfx(const char *id, int loops = 0) const;
      Result play_music(const char *id, int loops = 0) const;
      Result play_sfx(Mix_Chunk *sfx, int loops = 0) const;
      Result play_music(Mix_Music *music, int loops = 0) const;
      void pause_sfx() const;
      void pause_music() const;
      void resume_sfx() const;
      void resume_music() const;
      void stop_sfx() const;
      void stop_music() const;
      bool playing_sfx() const;
      bool playing_music() const;
      bool paused_sfx() const;
      bool paused_music() const;
      // valume 0-100.
      int volume_sfx() const;
      int volume_music() const;
      void set_volume_sfx(int v) const;
      void set_volume_music(int v) const;
      bool muted_sfx() const;
      bool muted_music() const;
      bool muted() const;
      void mute_sfx(bool mute);
      void mute_music(bool mute);
      void mute(bool mute);
    protected:
      std::map<std::string, Mix_Chunk*> sfxs_;
      std::map<std::string, Mix_Music*> musics_;
      int mute_volume_sfx_;
      int mute_volume_music_;
  };

  ///////////////////
  class Input {
    friend class Game;
    protected:
      Input();
      virtual ~Input();
    protected:
      Result init();
      void uninit();
      void update();
    public:
      bool key_pressed(SDL_Scancode key, bool once = true) const;
      bool key_released(SDL_Scancode key, bool once = true) const;
      bool key_mod(SDL_Keymod mod) const;

      int mouse_x() const;
      int mouse_y() const;
      SDL_Point mouse_point() const;
      bool mouse_pressed(int button, bool once = true) const;
      bool mouse_released(int button, bool once = true) const;

      int joy_axis(int joy, int axis) const;
      Uint8 joy_hat(int joy, int hat) const;
      bool joy_pressed(int joy, int button, bool once = true) const;
      bool joy_released(int joy, int button, bool once = true) const;

      int joy_count() const;
      int joy_axis_count() const;
      int joy_hat_count() const;
      int joy_button_count() const;
    protected:
      SDL_Keymod keymod_;
      Uint8 keys_[SDL_NUM_SCANCODES], keys_last_[SDL_NUM_SCANCODES];
      SDL_Point mouse_pt_;
      Uint32 mouses_btn_, mouses_btn_last_;
      std::vector<SDL_Joystick*> joysticks_;
      std::vector<std::vector<int>> joy_axis_;
      std::vector<std::vector<Uint8>> joy_hats_;
      std::vector<std::vector<bool>> joy_btns_, joy_btns_last_;
  };

  ///////////////////
  struct TextureData {
    std::string id;
    SDL_Rect src;
    TextureData() { memset(&src, 0, sizeof(src)); }
    TextureData(const char *id, const SDL_Rect *src = 0) { reset(id, src); }
    void reset(const char *id, const SDL_Rect *src) {
      if (id) TextureData::id = id;
      if (src) TextureData::src = *src;
    }
  };

  class Renderer {
    friend class Game;
    protected:
      Renderer();
      virtual ~Renderer();
    protected:
      Result init(SDL_Renderer *render, Font *font, Texture *texture);
      void uninit();
    public:
      // 0 for the default render target
      SDL_Texture* target() const;
      // 0 for the default render target
      Result set_target(SDL_Texture *t);
      // you must call destroy_target or manage it by Texture::load();
      SDL_Texture* create_target(int width, int height, Uint32 format = SDL_PIXELFORMAT_RGBA8888);
      void destroy_target(SDL_Texture *t);

      Result size(int *w, int *h) const;
      SDL_Rect viewport() const;
      Result set_viewport(const SDL_Rect *rect);
      SDL_Rect clip_rect() const;
      Result set_clip_rect(const SDL_FRect *rect);
      Result scale(float *x, float *y) const;
      Result set_scale(float x, float y);
      SDL_Color draw_color() const;
      Result set_draw_color(const SDL_Color &color);
      SDL_BlendMode blend_mode() const;
      Result set_blend_mode(SDL_BlendMode mode);
      void clear_draw(const SDL_Color *color = 0);
    protected:
      void cleardraw();
      void present();
    public:
      Result texture_size(const char *id, int *w, int *h) const;
      Result texture_size(SDL_Texture *t, int *w, int *h) const;
      Result text_size(const char *font, const char *text, int *w, int *h);
      Result text_size(TTF_Font *font, const char *text, int *w, int *h);
    public:
      Result draw_point(float x, float y);
      Result draw_points(const SDL_FPoint *points, int count);
      Result draw_line(float x1, float y1, float x2, float y2);
      Result draw_lines(const SDL_FPoint *points, int count);
      Result draw_rect(const SDL_FRect * rect);
      Result draw_rects(const SDL_FRect *rects, int count);
      Result fill_rect(const SDL_FRect * rect);
      Result fill_rects(const SDL_FRect *rects, int count);
      
      Result draw_texture(SDL_Texture *t, int x, int y, const SDL_Rect *src = 0);
      Result draw_texture(SDL_Texture *t, int x, int y, const SDL_Rect *src
          , const double angle, const SDL_FPoint *center = 0
          , const SDL_RendererFlip flip = SDL_FLIP_NONE);
      Result draw_texture(SDL_Texture *t, const SDL_FRect *dst, const SDL_Rect *src = 0);
      Result draw_texture(SDL_Texture *t, const SDL_FRect *dst, const SDL_Rect *src
          , const double angle, const SDL_FPoint *center = 0
          , const SDL_RendererFlip flip = SDL_FLIP_NONE);
      Result draw_texture(const char *id, int x, int y, const SDL_Rect *src = 0);
      Result draw_texture(const char *id, int x, int y, const SDL_Rect *src
          , const double angle, const SDL_FPoint *center = 0
          , const SDL_RendererFlip flip = SDL_FLIP_NONE);
      Result draw_texture(const char *id, const SDL_FRect *dst, const SDL_Rect *src = 0);
      Result draw_texture(const char *id, const SDL_FRect *dst, const SDL_Rect *src
          , const double angle, const SDL_FPoint *center = 0
          , const SDL_RendererFlip flip = SDL_FLIP_NONE);
      Result draw_texture(const TextureData *data, int x, int y);
      Result draw_texture(const TextureData *data, int x, int y
          , const double angle, const SDL_FPoint *center = 0
          , const SDL_RendererFlip flip = SDL_FLIP_NONE);
      Result draw_texture(const TextureData *data, const SDL_FRect *dst);
      Result draw_texture(const TextureData *data, const SDL_FRect *dst
          , const double angle, const SDL_FPoint *center = 0
          , const SDL_RendererFlip flip = SDL_FLIP_NONE);

      Result draw_text(TTF_Font *font, const char *text, const SDL_Color &fg, int x, int y);
      Result draw_text(TTF_Font *font, const char *text, const SDL_Color &fg, int x, int y
          , const double angle, const SDL_FPoint *center = 0
          , const SDL_RendererFlip flip = SDL_FLIP_NONE);
      Result draw_text(TTF_Font *font, const char *text, const SDL_Color &fg, const SDL_FRect *dst);
      Result draw_text(TTF_Font *font, const char *text, const SDL_Color &fg, const SDL_FRect *dst
          , const double angle, const SDL_FPoint *center = 0
          , const SDL_RendererFlip flip = SDL_FLIP_NONE);
      Result draw_text(const char *font, const char *text, const SDL_Color &fg, int x, int y);
      Result draw_text(const char *font, const char *text, const SDL_Color &fg, int x, int y
          , const double angle, const SDL_FPoint *center = 0
          , const SDL_RendererFlip flip = SDL_FLIP_NONE);
      Result draw_text(const char *font, const char *text, const SDL_Color &fg, const SDL_FRect *dst);
      Result draw_text(const char *font, const char *text, const SDL_Color &fg, const SDL_FRect *dst
          , const double angle, const SDL_FPoint *center = 0
          , const SDL_RendererFlip flip = SDL_FLIP_NONE);
    protected:
      SDL_Renderer *render_;
      Font *font_;
      Texture *texture_;
      SDL_Texture *target_;
  };

  ///////////////////
  class Game {
    protected:
      Game();
      virtual ~Game();
    public:
      virtual Result init(const char *title, int width, int height
          , Uint32 window_flags = SDL_WINDOW_SHOWN
          , Uint32 image_flags = IMG_INIT_PNG | IMG_INIT_JPG
          , Uint32 audio_flags = MIX_INIT_MP3 | MIX_INIT_MID);
      virtual void uninit();
      virtual void run();
      Uint32 fps() const;
      void set_fps(Uint32 fps);
      Uint32 real_fps() const;
    public:
      Font& font();
      Texture& texture();
      Audio& audio();
      Input& input();
      Renderer& renderer();
    public:
      Result set_window_size(int w, int h);
      Result window_size(int *w, int *h);
      Result close_window();
      Result enter_fullscreen(bool real = true);
      Result exit_fullscreen();
    protected:
      virtual Result load_resource(Font &f, Texture &t, Audio &a);
      virtual void unload_resource(Font &f, Texture &t, Audio &a);
      // return true if handled.
      virtual bool handle(const SDL_Event &evt);
      virtual void update(Input &inp, Audio &aud);
      virtual void render(Renderer &r);
    protected:
      SDL_Window *window_;
      SDL_Renderer *render_;
      Uint32 fps_, real_fps_;
      Uint32 delay_time_;

      Font font_;
      Texture texture_;
      Audio audio_;
      Input input_;
      Renderer renderer_;
  };
} // end namespace g2d

#endif // __GAME2D_GAME_H__